#
#       Author: Alexandros Sigalas
#
#      This program is free software; you can redistribute it and/or modify
#      it under the terms of the GNU General Public License as published by
#      the Free Software Foundation; either version 2 of the License, or
#      (at your option) any later version.
#      
#      This program is distributed in the hope that it will be useful,
#      but WITHOUT ANY WARRANTY; without even the implied warranty of
#      MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#      GNU General Public License for more details.
#      
#      You should have received a copy of the GNU General Public License
#      along with this program; if not, write to the Free Software
#      Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
#      MA 02110-1301, USA.

cimport cdefs as c

################## V E C T O R #########################################
################## V E C T O R #########################################
################## V E C T O R #########################################
################## V E C T O R #########################################

cdef c.vec asvec(object ob):
    cdef c.vec v
    if isinstance(ob, Vector):
        return Vector.value(ob)
    try:
        v.x = ob[0]
        v.y = ob[1]
        v.z = ob[2]
        
    except:
        raise TypeError("object cannot become vector")
    return v

cdef void asmatrix(c.matrix m, object ob):
    
    m[0][0]= ob[0][0]
    m[0][1]= ob[0][1]
    m[0][2]= ob[0][2]
    m[0][3]= ob[0][3]
    
    m[1][0]= ob[1][0]
    m[1][1]= ob[1][1]
    m[1][2]= ob[1][2]
    m[1][3]= ob[1][3]
    
    m[2][0]= ob[2][0]
    m[2][1]= ob[2][1]
    m[2][2]= ob[2][2]
    m[2][3]= ob[2][3]
    
    m[3][0]= ob[3][0]
    m[3][1]= ob[3][1]
    m[3][2]= ob[3][2]
    m[3][3]= ob[3][3]

cdef class Vector:
    cdef:
        c.vec *v
        readonly bint wrapped
        
    property x:
        def __get__(self):
            return self.v.x
        def __set__(self, double value):
            self.v.x = value

    property y:
        def __get__(self):
            return self.v.y
        def __set__(self, double value):
            self.v.y = value

    property z:
        def __get__(self):
            return self.v.z
        def __set__(self, double value):
            self.v.z = value

    property length:
        def __get__(self):
            return c.v_length(self.v)

    property normal:
        def __get__(self):
            cdef Vector normal= Vector()
            c.v_normal(normal.v, self.v)
            return normal

    def __cinit__(self, object co=[0.0,0.0,0.0]):
        self.wrapped = 0
        if co is None:
            self.v = NULL
        else:
            self.v = <c.vec *>c.malloc(sizeof(c.vec))
            self.v.x = <double>(co[0])
            self.v.y = <double>(co[1])
            self.v.z = <double>(co[2])
            

    def __dealloc__(self):
        if self.wrapped == 0:
            c.free(self.v)

    def __str__(self):
        cdef object s
        s = "[%.4f, %.4f, %.4f]"%(self.x, self.y, self.z)
        if self.wrapped:
            s += "(wrapped data)"
        s += "\n"
        return s

    def __add__(Vector x, Vector y):
        cdef Vector result= Vector()
        c.v_add(result.v, x.v, y.v);
        return result

    def __sub__(Vector x, Vector y):
        cdef Vector result= Vector()
        c.v_sub(result.v, x.v, y.v);
        return result

    def __mul__(x , y):
        cdef Vector result= Vector()
        if isinstance(x, Vector):
            if isinstance(y, Vector):
                return c.v_dot(Vector.pointer(x), Vector.pointer(y))
            else:
                c.v_scale(result.v, Vector.pointer(x), y)
        else:
            c.v_scale(result.v, Vector.pointer(y), x)
        return result
        
    def __iadd__(self, Vector other):
        c.v_add(self.v, self.v, other.v)
        return self

    def __isub__(self, Vector other):
        c.v_sub(self.v, self.v, other.v)
        return self

    def __imul__(self, double other):
        c.v_scale(self.v, self.v, other)
        return self

    def __getitem__(self, int i):
        cdef double *pt=<double *>self.v
        if 0 <= i < 3:
            return pt[i]
        else:
            raise IndexError("0 <= index < 3")

    def __setitem__(self, unsigned int i, double value):
        cdef double *pt= <double *>self.v
        if i < 3:
            pt[i] = value
        else:
            raise IndexError("0 <= i < 3")

    def dot(self, Vector other):
        return c.v_dot(self.v, other.v)

    def copy(self):
        if self.v == NULL:
            return Vector()
        return Vector([self.v.x, self.v.y, self.v.z])

    def same3D(self, object other):
        cdef c.vec tmp= asvec(other)
        return c.same_3d(self.v, &tmp)

    def same2D(self, object other):
        cdef c.vec tmp=  asvec(other)
        return c.same_2d(self.v, &tmp, c.XY)

    def perp(self, object other):
        cdef c.vec tmp=  asvec(other)
        return c.v_perp(self.v, &tmp, c.XY)

    def midTo(self, object other):
        cdef:
            c.vec tmp= asvec(other)
            Vector mid= Vector()
        c.midpoint(mid.v, self.v, &tmp)
        return mid

    def apply_matrix(self, object obj):
        cdef c.matrix mx
        asmatrix(mx, obj)
        c.v_mul_mx(self.v, self.v, mx)

    def rel2Plane(self, Vector pp, Vector pno):
        return c.rel_point_plane(self.v, pp.v, pno.v)

    cdef void wrap(self, c.vec* v):
        if self.wrapped:
            pass
        else:
            c.free(self.v)
            self.wrapped= 1
        self.v= v

    cdef void grab(self, c.vec* v):
        if self.wrapped:
            self.wrapped= 0
        else:
            c.free(self.v)
        self.v = v

    cdef c.vec value(self):
        return self.v[0]

    cdef c.vec* pointer(self):
        return self.v

########################## B O U N D S #################################
########################## B O U N D S #################################
########################## B O U N D S #################################

cdef class Bounds:
    cdef c.bounds b

    property xmin:
        def __get__(self):
            return self.b.min.x

    property ymin:
        def __get__(self):
            return self.b.min.y

    property zmin:
        def __get__(self):
            return self.b.min.z

    property xmax:
        def __get__(self):
            return self.b.max.x

    property ymax:
        def __get__(self):
            return self.b.max.y

    property zmax:
        def __get__(self):
            return self.b.max.z
    
    def __cinit__(self, object ob):
        cdef:
            unsigned int i
            c.vec tmp
        if ob is None:
            self.b= NULL;
        else:
            self.b= <c.bounds>c.malloc(sizeof(c.Bounds))
            tmp= asvec(ob[0])
            c.v_copy(&self.b.min, &tmp)
            c.v_copy(&self.b.max, &tmp)
            for i from 0 < i < len(ob):
                tmp= asvec(ob[i])
                c.check_bounds(self.b, &tmp)
    
    def __dealloc__(self):
        c.free(self.b)
    
    def disjoint2D(self, object other):
        cdef Bounds tmp
        if other is None:
            raise ValueError("Object is None")
        elif isinstance(other, Bounds):
            return c.disjoint_2d(self.b, Bounds.pointer(other), c.XY)
        else:
            tmp= Bounds(other)
            return c.disjoint_2d(self.b, tmp.b, c.XY)
        
    def disjoint3D(self, object other):
        cdef Bounds tmp
        if other is None:
            raise ValueError("Object is None")
        elif isinstance(other, Bounds):
            return c.disjoint_3d(self.b, Bounds.pointer(other))
        else:
            tmp= Bounds(other)
            return c.disjoint_3d(self.b, tmp.b)
    
    def pointInside3D(self, object other):
        cdef c.vec tmp= asvec(other)
        return c.point_in_bounds(self.b, &tmp)
    
    def pointInside2D(self, object other):
        cdef c.vec tmp= asvec(other)
        return c.point_in_bounds_2d(self.b, &tmp, c.XY)
    
    cdef c.bounds pointer(self):
        return self.b

cdef Bounds b2B(c.bounds bb):
    cdef Bounds result= Bounds(None)
    result.b= bb
    return result

########################## M A T E R I A L #############################
########################## M A T E R I A L #############################
########################## M A T E R I A L #############################

cdef c.col ascolor(object obj):
    pass


cdef class Material:
    cdef:
        c.material mat
        readonly bint wrapped

    property name:
        def __get__(self):
            return self.mat.name
        def __set__(self, char* value):
            c.ma_rename(self.mat, value)

    property alpha:
        def __get__(self):
            return self.mat.alpha
        def __set__(self, float value):
            self.mat.alpha= value

    property lineweight:
        def __get__(self):
            return self.mat.lineweight
        def __set__(self, float value):
            self.mat.lineweight= value

    property fill_color:
        def __get__(self):
            return [self.mat.fill_color.r, self.mat.fill_color.g, self.mat.fill_color.b]
        def __set__(self, object value):
            cdef c.ColorF tmp= ascolor(value)
            self.mat.fill_color= tmp

    property line_color:
        def __get__(self):
            return [self.mat.line_color.r, self.mat.line_color.g, self.mat.line_color.b]
        def __set__(self, object value):
            self.mat.line_color= ascolor(value)
    
    property section:
        def __get__(self):
            if self.mat.section == NULL:
                return None
            cdef Material result= Material()
            result.wrap(self.mat.section)
            return result
        
        def __set__(self, Material value):
            self.mat.section= value.mat
    
    property hidden:
        def __get__(self):
            if self.mat.hidden == NULL:
                return None
            cdef Material result= Material()
            result.wrap(self.mat.hidden)
            return result
        
        def __set__(self, Material value):
            self.mat.hidden= value.mat
    
    def __cinit__(self, object name, float lineweight= 0.2, \
                                     float alpha= 1.0,\
                                     object line_color=[0.0,0.0,0.0],\
                                     object fill_color=[1.0,1.0,1.0],\
                                     Material section= None,\
                                     Material hidden= None):
        cdef char* name_str= name
        if name is None:
            self.mat= NULL
        else:
            self.mat= c.ma_init(name_str)
            self.mat.alpha= alpha
            self.mat.lineweight= lineweight
            self.mat.line_color= ascolor(line_color)
            self.mat.fill_color= ascolor(fill_color)
        
        if not section is None:
            self.mat.section= section.mat
                    
        if not hidden is None:
            self.mat.hidden= hidden.mat
            
    def __dealloc__(self):
        pass

    cdef void wrap(self, c.material mat):
        pass

    cdef void grab(self, c.material mat):
        pass

    cdef c.material* pointer(self):
        return &self.mat

    cdef c.material data(self):
        return self.mat

########################## P O L Y G O N ###############################
########################## P O L Y G O N ###############################
########################## P O L Y G O N ###############################
########################## P O L Y G O N ###############################

cdef class PolyVert:
    cdef:
        c.PL_Vertex *vx
        readonly bint wrapped

    property co:
        def __get__(self):
            cdef Vector result= Vector(None)
            result.wrap(&self.vx.co)
            return result
        
        def __set__(self, object value):
            cdef c.vec tmp= asvec(value)
            c.v_copy(&self.vx.co, &tmp)
    
    property flags:
        def __get__(self):
            return self.vx.flags
        def __set__(self, int value):
            self.vx.flags= value
    
    def __cinit__(self, object co, int flags=0):
        cdef c.vec tmp
        self.wrapped= 0
        if co is None:
            self.vx= NULL
        else:
            self.vx= <c.PL_Vertex *>c.malloc(sizeof(c.PL_Vertex))
            tmp= asvec(co)
            c.v_copy(&self.vx.co, &tmp)
            self.vx.flags= flags

    def __str__(self):
        return "Vertex Oject" #TODO: Vertex string output for debugging
    
    cdef void wrap(self, c.PL_Vertex *vx):
        if not self.wrapped:
            c.free(self.vx)
            self.wrapped= 1
        self.vx= vx

    def __dealloc__(self):
        if not self.wrapped:
            c.free(self.vx)

cdef class Poly

cdef class PolyList:
    cdef:
        c.PL_List **pll_pt
        unsigned int index
        readonly bint wrapped
        
    def __cinit__(self, list polygons=list()):
        self.wrapped= 0
        self.index= 0
        self.pll_pt= <c.PL_List **>c.malloc(sizeof(c.PL_List*))

        cdef unsigned int i, limit
        if polygons is None:
            self.pll_pt[0]= NULL
        if not polygons:
            self.pll_pt[0]= c.pll_init(10)
        else:
            limit= len(polygons)
            self.pll_pt[0]= c.pll_init(limit)
            for i from 0 <= i < limit:
                self.append(polygons[i])

    def __dealloc__(self):
        if not self.wrapped:
            c.pll_killall(self.pll_pt)
        c.free(self.pll_pt)
    
    def __getitem__(self, unsigned int i):
        if (self.pll_pt[0] == NULL):
            raise IndexError("PolyList holds no data")
        if (self.pll_pt[0].last <= i):
            raise IndexError("Index out of bounds")
        cdef Poly result= Poly(coords=None)
        result.wrap(self.pll_pt[0].polys[i])
        return result

    def __setitem__(self, unsigned int i, Poly data):
        cdef c.polygon tmp
        if self.pll_pt[0] == NULL:
            raise IndexError("PolyList holds no data")
        if (i < self.pll_pt[0].last):
            tmp= c.pll_replace(self.pll_pt[0], i, data.pl)
            c.pl_kill(&tmp)
        else:
            raise IndexError("Index out of bounds")

    def __next__(self):
        cdef Poly result= Poly(coords=None)
        if self.index == self.pll_pt[0].last:
            self.index= 0
            raise StopIteration
        else:
            result.wrap(self.pll_pt[0].polys[self.index])
            self.index += 1
            return result

    cpdef append(self, Poly pl):
        c.pll_append(self.pll_pt, pl.pl)

    cdef wrap(self, c.polylist *data):
        if not self.wrapped:
            c.pll_killall(self.pll_pt)
            self.wrapped= 1
        self.pll_pt= data;

    cdef grab(self, c.polylist data):
        if self.wrapped:
            self.wrapped= 0
        else:
            c.pll_killall(self.pll_pt)
        self.pll_pt[0]= data

cdef PolyList pll2PLL(c.polylist pll):
    cdef PolyList result= PolyList(polygons=None)
    result.grab(pll)
    return result


cdef class Poly:
    cdef:
        c.polygon pl
        unsigned int index
        readonly bint wrapped
        
    # properties ################
    property bounds:
        def __get__(self):
            return b2B(&self.pl.bb)
            
    property area_3d:
        def __get__(self):
            return c.pl_area_3d(self.pl, 1)

    property area_2d:
        def __get__(self):
            return c.pl_area_2d(self.pl, c.XY, 1)

    property normal:
        def __get__(self):
            cdef Vector result= Vector()
            c.pl_normal(self.pl, result.v)
            return result

    property attr:
        def __get__(self):
            return self.pl.attr
        def __set__(self, int value):
            self.pl.attr= value

    property flag:
        def __get__(self):
            return self.pl.flag
        def __set__(self, bint value):
            self.pl.flag= value

    property shade_f:
        def __get__(self):
            return self.pl.props.shade_f
        def __set__(self, float value):
            self.pl.props.shade_f= value

    property coplanar_id:
        def __get__(self):
            return self.pl.props.coplanar_id
        def __set__(self, unsigned int value):
            self.pl.props.coplanar_id= value

    property crease_id:
        def __get__(self):
            return self.pl.props.crease_id
        def __set__(self, unsigned int value):
            self.pl.props.crease_id= value

    property order:
        def __get__(self):
            return c.pl_order(self.pl)

    property holes:
        def __get__(self):
            cdef PolyList result= PolyList(polygons=None)
            result.wrap(&self.pl.holes)
            return result
    
    property material:
        def __get__(self):
            return Material()

    # methods ################
    def __cinit__(self, object coords, int attr=0, object props=None, object mat=None):

        self.index= 0
        self.wrapped=0
        cdef unsigned int i, limit, size
        cdef c.vec co
        if coords is None:
            self.pl= NULL
        else:
            limit= len(coords)
            size= 100 if limit < 10 else 2 * limit
            self.pl= c.pl_init(size)
            for i from 0 <= i < limit:
                co= asvec(coords[i])
                c.pl_append(&self.pl, &co, 0)
        
    cdef c.polygon pointer(self):
        return self.pl

    def __dealloc__(self):
        c.pl_kill(&self.pl)

    def __next__(self):
        # returns wrapped Vector object
        cdef:
            PolyVert vx= PolyVert(co=None)
            c.PL_Vertex *v= self.pl.points + self.index

        if self.index == self.pl.last:
            self.index = 0
            raise StopIteration
        else:
            vx.wrap(v)
            self.index += 1
            return vx

    def __str__(self):
        cdef object s
        s = "Polygon:\n"
        for p in self:
            s += "\t" + str(p) + "\n"
        return s

    def __len__(self):
        return self.pl.last

    def __getitem__(self, unsigned int i):
        # returns wrapped Vector object
        cdef:
            PolyVert v= PolyVert(None)
            c.PL_Vertex *vx

        if self.pl == NULL:
            raise IndexError("Poly holds no data")
        elif i < self.pl.last:
            vx= self.pl.points + i
            v.wrap(vx)
            return v
        else:
            raise IndexError("index out of bounds")

    def __setitem__(self, int i, object p):
        cdef c.vec tmp
        if i < self.pl.last:
            tmp= asvec(p)
            c.v_copy(&self.pl.points[i].co, &tmp)
        else:
            raise IndexError()

    def copy(self, bint holes=True):
        return pl2PL(c.pl_copy(self.pl, NULL, holes))

    def append(self, object co, int flags=0):
        cdef c.vec pt= asvec(co)
        c.pl_append(&self.pl, &pt, flags);

    def addHole(self, Poly hole):
        return c.pl_add_hole(self.pl, hole.pl)

    def reverse(self):
        c.pl_reverse(self.pl)

    def relPoint2D(self, Vector p, bint holes=False):
        return c.pl_rel_point_2d(self.pl, p.v, c.XY, holes)

    def disjoint2D(self, Poly other):
        return c.disjoint_2d(&self.pl.bb, &other.pl.bb, c.XY)

    def disjoint3D(self, Poly other):
        return c.disjoint_3d(&self.pl.bb, &other.pl.bb)

    def swap2D(self, int axp=c.XY):
        cdef c.polygon old_pl= self.pl
        if axp == c.XZ:
            self.pl= c.pl_copy(old_pl, c.v_swp_xz, 1)
        elif axp == c.YZ:
            self.pl= c.pl_copy(old_pl, c.v_swp_yz, 1)
        else:
            self.pl= c.pl_copy(old_pl, c.v_swp_xy, 1)
        c.pl_kill(&old_pl)

    def prj2Plane(self, Vector pp, Vector pn, Vector direction):
        c.pl_prj_to_plane(self.pl, pp.v, pn.v, direction.v)

    def prj2Poly(self, Poly other, Vector direction):
        self.prj2Plane(other[0].co, other.normal, direction)

    def split2Plane(self, Vector vpp, Vector vpn):
        cdef c.polylist fparts= NULL
        cdef c.polylist bparts= NULL
        c.pl_split_by_plane(self.pl, vpp.v, vpn.v, &fparts, &bparts, 1)
        return pll2PLL(fparts), pll2PLL(bparts)
        
    def removeDoubles2D(self, c.axis_pair axp=c.XY):
        return c.pl_rm_doubles_2d(self.pl, axp)

    def removeDoubles3D(self):
        return c.pl_rm_doubles_3d(self.pl)

    def simplify2D(self, c.axis_pair axp=c.XY):
        return c.pl_simplify_2d(self.pl, axp)

    def simplify3D(self):
        return c.pl_simplify_3d(self.pl)

    def flattenZ(self):
        cdef c.polygon old_pl= self.pl
        self.pl= c.pl_copy(self.pl, c.v_flat_z, 1)
        c.pl_kill(&old_pl)
    
    def flattenPersp(self):
        cdef c.polygon old_pl= self.pl
        self.pl= c.pl_copy(self.pl, c.v_flat_persp, 1)
        c.pl_kill(&old_pl)

    cdef void grab(self, c.polygon data):
        if self.wrapped:
            self.wrapped= 0
        else:
            c.pl_kill(&self.pl)
        self.pl= data
        
    cdef void wrap(self, c.polygon data):
        if not self.wrapped:
            self.wrapped= 1
            c.pl_kill(&self.pl)
        self.pl= data

cdef Poly pl2PL(c.polygon pl):
    cdef Poly result= Poly(None)
    result.grab(pl)
    return result

######################### B O O L E A N ################################
######################### B O O L E A N ################################
######################### B O O L E A N ################################

bool_op= {"A-B":c.SUBTRACT_A_B,\
           "B-A":c.SUBTRACT_B_A,\
           "A+B":c.UNION,\
           "A|B":c.INTERSECT}

cdef class BooleanGraph:
    cdef c.BL_Graph *g
    property rel_a:
        def __get__(self):
            return self.g.pl_a.rel

    property rel_b:
        def __get__(self):
            return self.g.pl_b.rel
        
    def __cinit__(self, Poly pl_a, Poly pl_b):
        self.g= c.bl_build_graph(pl_a.pl, pl_b.pl)
    
    def __str__(self):
        return "BooleanGraph" #TODO:BooleanGraph string output for debugging
    
    def __call__(self, op):
        if op != c.SUBTRACT_B_A and\
           op != c.SUBTRACT_A_B and\
           op != c.UNION and\
           op != c.INTERSECT:
               raise ValueError("Unsupported operation")
        cdef PolyList result= PolyList(polygons=None)
        result.grab(c.bl_operation(self.g, op))
        return result
        
        
############################ M E S H ###################################
############################ M E S H ###################################
############################ M E S H ###################################
############################ M E S H ###################################

cdef class MEdge:
    cdef c.medge ed
        
    property v0:
        def __get__(self):
            cdef MVert result= MVert()
            result.wrap(self.ed.v0)
            return result
    
    property v1:
        def __get__(self):
            cdef MVert result= MVert()
            result.wrap(self.ed.v1)
            return result

    property users:
        def __get__(self):
            cdef list result= list()
            cdef unsigned int i
            cdef MFace tmp
            for i from 0 <= i < self.ed.users.last:
                tmp= MFace()
                tmp.wrap(self.ed.users.faces[i])
                result.append(tmp)
            return result
            
    property flags:
        def __get__(self):
            return self.ed.flags
        def __set__(self, int value):
            self.ed.flags= value
    
    def __cinit__(self):
        self.ed= NULL
    
    cdef void wrap(self, c.medge data):
        self.ed= data


cdef class MVert:
    cdef c.mvert vx

    property co:
        def __get__(self):
            cdef Vector result= Vector(None)
            result.wrap(&self.vx.co)
            return result
        def __set__(self, object value):
            cdef c.vec tmp= asvec(value)
            c.v_copy(&self.vx.co, &tmp)
    
    property flags:
        def __get__(self):
            return self.ed.flags
        def __set__(self, int value):
            self.ed.flags= value    
    
    def __cinit__(self):
        self.vx= NULL
        
    cdef void wrap(self, c.mvert data):
        self.vx= data

cdef class MFace:
    cdef c.mface mf
    property verts:
        def __get__(self):
            cdef:
                list result= list()
                MVert tmp
                unsigned int i
            for i from 0 <= i < 3:
                tmp= MVert()
                tmp.wrap(self.mf.verts[i])
                result.append(tmp)
            return result
    
    property flags:
        def __get__(self):
            return self.ed.flags
        def __set__(self, int value):
            self.ed.flags= value
    
    property edges:
        def __get__(self):
            cdef:
                list result= list()
                MEdge tmp
                unsigned int i
            for i from 0 <= i < 3:
                tmp= MEdge()
                tmp.wrap(self.mf.edges[i])
                result.append(tmp)
            return result
        
    cdef void wrap(self, c.mface data):
        self.mf= data

cdef class MVertList:
    cdef:
        c.mesh me
        unsigned int index
    def __cinit__(self):
        self.me= NULL
        self.index= 0
        
    def __next__(self):
        cdef MVert result= MVert()
        if self.index < self.me.last_v:
            result.wrap(self.me.verts + self.index)
            self.index+=1
            return result
        else:
            self.index= 0
            raise StopIteration
        
    def __len__(self):
        return self.me.last_v
    
    def __getitem__(self, unsigned int i):
        cdef MVert tmp= MVert()
        tmp.wrap(self.me.verts + i)
        return tmp
    
    def append(self, object co, int flags=0):
        cdef c.vec tmp= asvec(co)
        cdef c.mvert vx= c.me_add_vert(self.me, &tmp)
        vx.flags= flags

    def extend(self, object verts):
        cdef:
            object vert
            c.vec co
        for vert in verts:
            co= asvec(vert)
            c.me_add_vert(self.me, &co)

    def delete(self, unsigned int i):
        if not c.me_del_vert(self.me, i):
            raise ValueError("invalid index")
            
    def kill(self, unsigned int i):
        if not c.me_del_vert(self.me, i):
            raise ValueError("invalid index")
    
    cdef void wrap(self, c.mesh me):
        self.me= me

cdef class MEdgeList:
    cdef:
        c.mesh me
        unsigned int index
        
    def __cinit__(self):
        self.me= NULL
        self.index= 0
        
    def __next__(self):
        cdef MEdge result= MEdge()
        if self.index < self.me.last_e:
            result.wrap(self.me.edges + self.index)
            self.index+=1
            return result
        else:
            self.index= 0
            raise StopIteration
        
    def __len__(self):
        return self.me.last_e
    
    def __getitem__(self, unsigned int i):
        cdef MEdge tmp= MEdge()
        tmp.wrap(self.me.edges + i)
        return tmp
    
    def append(self, object ed, int flags=0):
        cdef c.medge new_edge= c.me_add_edge(self.me, ed[0], ed[1])
        if new_edge == NULL:
            raise ValueError("invalid indicies")
        else:
            new_edge.flags= flags
    
    def extend(self, object edges):
        cdef object ed
        for ed in edges:
            if c.me_add_edge(self.me, ed[0], ed[1]) == NULL:
                raise ValueError("wrong indicies")
        
    def delete(self, unsigned int i):
        if not c.me_del_edge(self.me, i):
            raise ValueError("wrong index")
    
    def kill(self, unsigned int i):
        if not c.me_kill_edge(self.me, i):
            raise ValueError("wrong index")
    
    cdef void wrap(self, c.mesh me):
        self.me= me


cdef class MFaceList:
    cdef:
        c.mesh me
        unsigned int index
        
    def __cinit__(self):
        self.me= NULL
        self.index= 0
        
    def __next__(self):
        cdef MFace result= MFace()
        if self.index < self.me.last_f:
            result.wrap(self.me.faces + self.index)
            self.index+= 1
            return result
        else:
            self.index= 0
            raise StopIteration
        
    def __len__(self):
        return self.me.last_f
    
    def __getitem__(self, unsigned int i):
        cdef MFace tmp= MFace()
        tmp.wrap(self.me.faces + i)
        return tmp
    
    def append(self, object f, int flags=0):
        cdef c.mface new_face= c.me_add_face(self.me, f[0], f[1], f[2])
        if new_face == NULL:
            raise ValueError("invalid indicies")
        else:
            new_face.flags= flags
    
    def extend(self, object faces):
        cdef:
            object face
            
        for face in faces:
            if not c.me_add_face(self.me, face[0], face[1], face[2]):
                raise ValueError("Wrong indicies")
   
    def delete(self, unsigned int i):
        if not c.me_del_face(self.me, i):
            raise ValueError("Wrong index")

    def kill(self, unsigned int i):
        if not c.me_kill_face(self.me, i):
            raise ValueError("Wrong index")

    cdef void wrap(self, c.mesh me):
        self.me= me

cdef class Mesh:
    cdef c.mesh me
    property edges:
        def __get__(self):
            cdef MEdgeList result= MEdgeList()
            result.wrap(self.me)
            return result
    property verts:
        def __get__(self):
            cdef MVertList result= MVertList()
            result.wrap(self.me)
            return result
    property faces:
        def __get__(self):
            cdef MFaceList result= MFaceList()
            result.wrap(self.me)
            return result

    def __cinit__(self):
        self.me= NULL

    cdef void wrap(self, c.mesh me):
        self.me= me

    def toPolys(self, double threshold, unsigned int cop_id):
        cdef c.polylist pll= c.me_to_polys(self.me, threshold, &cop_id)
        return pll2PLL(pll)
    
